Procesamiento de XML
====================
XML es un lenguaje de marcado para escribir documentos estructurados. Se
usa para multitud de aplicaciones y por tanto deben conocerse al menos
los rudimentos.
Un documento XML está jerárquicamente dividido por *elementos*
identificados por etiquetas (*tags*). La etiqueta que marca el comienzo
se identifica con una palabra entre corchetes angulares, mientras que la
etiqueta que marca el final se identifica con un carácter ``/``
adicional antes de la misma palabra. Por ejemplo:
.. raw:: html
<root>
...
</root>
Dentro de un elemento puede haber otros elementos. Por ejemplo:
.. raw:: html
<root>
<origen>...</origen>
<nombre>...</nombre>
</root>
Cada etiqueta puede tener atributos que se incluyen justo después de la
etiqueta de comienzo con los valores entre comillas:
.. raw:: html
<root id="28013" version="1.0">
...
</root>
Un elemento también puede contener texto entre las etiquetas de comienzo
y fin:
.. raw:: html
<nombre>Aranjuez</nombre>
Un documento XML bien formado solo contiene un elemento raíz (no
contenido en ningún otro elemento). Por tanto al leer un documento XML
se genera un arbol de elementos.
La biblioteca estándar de Python incluye `varios
módulos `__ relacionados con
la lectura y escritura de documentos XML. Nosotros solo comentaremos el
más sencillo. Consulta la `documentación de
Python `__ si necesitas
ampliar más.
Leer con ``ElementTree``
------------------------
El procesador de XML más simple y ligero de los incluidos en Python es
``xml.etree.ElementTree``. Ilustraremos su uso con un archivo XML como
los empleados en el trabajo en grupo.
.. code:: python
import requests
r = requests.get('http://www.aemet.es/xml/municipios/localidad_28013.xml')
xml = r.text.encode('utf-8')
Ahora la variable ``xml`` contiene una cadena con todo el documento XML.
Veamos cómo interpretarlo con ``ElementTree``.
.. code:: python
import xml.etree.ElementTree as ET
root = ET.fromstring(xml)
La función ``fromstring`` devuelve un objeto de tipo ``Element``, el
elemento raíz del documento XML. ``Element`` permite acceder a todos los
componentes de un elemento con estas funciones:
- ``e.iter('etiqueta')`` devuelve todos los elementos ``etiqueta`` que
haya dentro del elemento ``e``. Incluso los que estén dentro de otros
elementos contenidos dentro de ``e``.
- ``e.find('etiqueta')`` devuelve el primer elemento ``etiqueta`` que
es hijo directo de ``e``.
- ``e.findall('etiqueta')`` devuelve todos los elementos ``etiqueta``
que son hijos directos de ``e``.
- ``e.text`` devuelve la cadena correspondiente al texto del elemento
``e``.
- ``e.attrib`` devuelve los atributos del elemento ``e`` como un
diccionario.
- ``e.get('attr')`` devuelve el valor del atributo ``attr`` del
elemento ``e``.
Veamos un ejemplo que imprime la humedad mínima y máxima para los
próximos días.
.. code:: python
for dia in root.iter('dia'):
print '{0}:'.format(dia.get('fecha')),
humedad = dia.find('humedad_relativa')
hmax = int(humedad.find('maxima').text)
hmin = int(humedad.find('minima').text)
print 'humedad {0}/{1}'.format(hmin, hmax)
.. parsed-literal::
2015-12-25: humedad 70/100
2015-12-26: humedad 55/100
2015-12-27: humedad 45/100
2015-12-28: humedad 60/95
2015-12-29: humedad 75/100
2015-12-30: humedad 65/95
2015-12-31: humedad 90/100
Leer con ``BeautifulSoup``
--------------------------
**Nota: En *BeautifulSoup* se usan atributos de Python para representar
elementos XML. No confundas los *atributos* de Python con los
*atributos* de un elemento XML. Los atributos de XML son parejas
clave/valor que se pueden incluir en las etiquetas de comienzo de cada
elemento.**
*BeautifulSoup* es una biblioteca que simplifica notablemente la lectura
y escritura de documentos XML. En *BeautifulSoup* la jerarquía del
documento se traslada automáticamente a Python en forma de atributos de
objeto. Así, por ejemplo, si el documento está contenido en un elemento
``root`` entonces lo devuelto por *BeautifulSoup* tendrá un atributo
``root``.
.. code:: python
from bs4 import BeautifulSoup
soup = BeautifulSoup(xml, 'lxml')
Así, ``soup`` tendrá un atributo ``root`` y a su vez ese atributo tendrá
un atributo ``prediccion``, etc. Tenemos varias formas de recorrer los
elementos. Si usamos los paréntesis como si ``soup`` fuera una función
podemos buscar todos los elementos con una etiqueta determinada. Por
ejemplo, ``soup('dia')`` nos devolverá todos los elementos con etiqueta
``dia``. En cambio si usamos los corchetes, como si se tratara de una
lista, podemos acceder a los atributos del elemento. Por ejemplo si
``dia`` es un elemento con etiqueta ``'dia'`` entonces ``dia['fecha']``
es el valor del atributo ``fecha`` del elemento ``dia`` en el documento
XML.
Si solo hay un elemento con esa etiqueta entonces podemos usar el
atributo con el mismo nombre. Por ejemplo, los elementos con etiqueta
``dia`` tienen solo un elemento ``humedad_relativa``. Por tanto podemos
acceder a él usando el atributo del mismo nombre. Si hay múltiples
elementos con la misma etiqueta el atributo solo sirve para acceder al
primero.
Para obtener el texto de cada elemento podemos acceder al atributo
``string``. Por ejemplo,
``soup.root.dia.prediccion.humedad_relativa.maxima.string`` es el texto
del elemento ``maxima``, dentro del elemento ``humedad_relativa``,
dentro del elemento ``prediccion``, dentro del primer elemento ``dia``,
dentro del elemento ``root`` del documento.
Así, el código equivalente al ejemplo de ``ElementTree`` sería:
.. code:: python
for dia in soup('dia'):
humedad = dia.humedad_relativa
hmax = int(humedad.maxima.string)
hmin = int(humedad.minima.string)
print '{0}: humedad {1}/{2}'.format(dia['fecha'], hmin, hmax)
.. parsed-literal::
2015-12-25: humedad 70/100
2015-12-26: humedad 55/100
2015-12-27: humedad 45/100
2015-12-28: humedad 60/95
2015-12-29: humedad 75/100
2015-12-30: humedad 65/95
2015-12-31: humedad 90/100